﻿using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Zeus.Mvc.InputFormatters
{
    using Internal;
    public class ZeusInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
    {
        private readonly IArrayPool<char> _charArrayPool;
        private readonly ILogger _logger;
        private readonly ObjectPoolProvider _objectPoolProvider;

        private ObjectPool<JsonSerializer> _jsonSerializerPool;

        protected JsonSerializerSettings JsonSerializerSettings {
            get;
        }

        public ZeusInputFormatter(ArrayPool<char> charArrayPool, ObjectPoolProvider objectPoolProvider, JsonSerializerSettings jsonSerializerSettings, ILogger<ZeusInputFormatter> logger)
        {
            _charArrayPool = new ZeusArrayPool<char>(charArrayPool);
            _logger = logger;
            _objectPoolProvider = objectPoolProvider;


            JsonSerializerSettings = jsonSerializerSettings;

            SupportedEncodings.Add(UTF8EncodingWithoutBOM);
            SupportedEncodings.Add(UTF16EncodingLittleEndian);
            SupportedMediaTypes.Add(ZeusMediaTypeHeaderValues.ApplicationJson);
        }

        public InputFormatterExceptionPolicy ExceptionPolicy {
            get {
                if (GetType() == typeof(ZeusInputFormatter))
                {
                    return InputFormatterExceptionPolicy.MalformedInputExceptions;
                }
                return InputFormatterExceptionPolicy.AllExceptions;
            }
        }

        public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
        {

            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (encoding == null)
            {
                throw new ArgumentNullException(nameof(encoding));
            }
            var request = context.HttpContext.Request;
            var suppressInputFormatterBuffering = false;

            if (!request.Body.CanSeek && !suppressInputFormatterBuffering)
            {
                request.EnableBuffering();
                Debug.Assert(request.Body.CanSeek);

                await request.Body.DrainAsync(CancellationToken.None);
                request.Body.Seek(0L, SeekOrigin.Begin);
            }

            using (var streamReader = context.ReaderFactory(request.Body, encoding))
            {
                using (var jsonReader = new JsonTextReader(streamReader))
                {
                    jsonReader.ArrayPool = _charArrayPool;
                    jsonReader.CloseInput = false;

                    var successful = true;
                    Exception exception = null;
                    void SerializerErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs errorEventArgs)
                    {
                        successful = false;

                        var key = errorEventArgs.ErrorContext.Path;
                        if (!string.IsNullOrWhiteSpace(context.ModelName))
                        {
                            if (string.IsNullOrEmpty(errorEventArgs.ErrorContext.Path))
                            {
                                key = context.ModelName;
                            }
                            else if (errorEventArgs.ErrorContext.Path[0] == '[')
                            {
                                key = context.ModelName + errorEventArgs.ErrorContext.Path;
                            }
                            else
                            {
                                key = context.ModelName + "." + errorEventArgs.ErrorContext.Path;
                            }
                        }

                        var metadata = GetPathMetadata(context.Metadata, errorEventArgs.ErrorContext.Path);
                        exception = errorEventArgs.ErrorContext.Error;
                        context.ModelState.TryAddModelError(key, exception, metadata);

                        _logger.LogError(exception, exception.Message);
                        errorEventArgs.ErrorContext.Handled = true;
                    }

                    var type = context.ModelType;
                    var jsonSerializer = CreateJsonSerializer();
                    jsonSerializer.Error += SerializerErrorHandler;
                    object model;
                    try
                    {
                        model = jsonSerializer.Deserialize(jsonReader, type);
                    }
                    finally
                    {
                        jsonSerializer.Error -= SerializerErrorHandler;
                        ReleaseJsonSerializer(jsonSerializer);
                    }

                    if (successful)
                    {
                        if (model == null && !context.TreatEmptyInputAsDefaultValue)
                        {
                            return InputFormatterResult.NoValue();
                        }
                        else
                        {
                            return InputFormatterResult.Success(model);
                        }
                    }
                    else {
                        if (!(exception is JsonException || exception is OverflowException))
                        {
                            var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
                            exceptionDispatchInfo.Throw();
                        }

                        return InputFormatterResult.Failure();
                    }
                }
            }
        }

        private ModelMetadata GetPathMetadata(ModelMetadata metadata, string path)
        {
            var index = 0;
            while (index >= 0 && index < path.Length)
            {
                if (path[index] == '[')
                {
                    // At start of "[0]".
                    if (metadata.ElementMetadata == null)
                    {
                        // Odd case but don't throw just because ErrorContext had an odd-looking path.
                        break;
                    }

                    metadata = metadata.ElementMetadata;
                    index = path.IndexOf(']', index);
                }
                else if (path[index] == '.' || path[index] == ']')
                {
                    // Skip '.' in "prefix.property" or "[0].property" or ']' in "[0]".
                    index++;
                }
                else
                {
                    // At start of "property", "property." or "property[0]".
                    var endIndex = path.IndexOfAny(new[] { '.', '[' }, index);
                    if (endIndex == -1)
                    {
                        endIndex = path.Length;
                    }

                    var propertyName = path.Substring(index, endIndex - index);
                    if (metadata.Properties[propertyName] == null)
                    {
                        // Odd case but don't throw just because ErrorContext had an odd-looking path.
                        break;
                    }

                    metadata = metadata.Properties[propertyName];
                    index = endIndex;
                }
            }

            return metadata;
        }

        /// <summary>
        /// 获取JsonSerializer
        /// </summary>
        /// <returns></returns>
        protected virtual JsonSerializer CreateJsonSerializer()
        {
            if (_jsonSerializerPool == null)
            {
                _jsonSerializerPool = _objectPoolProvider.Create(new JsonSerializerObjectPolicy(JsonSerializerSettings));
            }
            return _jsonSerializerPool.Get();
        }

        /// <summary>
        /// 释放JsonSerializer
        /// </summary>
        /// <param name="serializer"></param>
        protected virtual void ReleaseJsonSerializer(JsonSerializer serializer)
            => _jsonSerializerPool.Return(serializer);
    }
}
